
#include "GlueTypes.h"
#include "PCM_sourceGlue.h"
#include "PCM_sinkGlue.h"
#include "ProjectStateGlue.h"
#include "MidiIOGlue.h"

#include <assert.h>

// you can go ahead and make NULL lightuserdatas
// for future or dummy use, push/check will not
// check for that condition, only that the
// type is LUA_TLIGHTUSERDATA

void pushLud(lua_State *L, void *ptr)
{
	lua_pushlightuserdata(L, ptr);
}

void *checkLud(lua_State *L, int ludidx)
{
	luaL_checktype(L, ludidx, LUA_TLIGHTUSERDATA);
	void *ptr = lua_touserdata(L, ludidx);
	return ptr;
}

// IFF the UD() at the top of the stack has a metatable
// that matches the one in the registry with the metaname
// then leave the string on the stack

bool typeName(lua_State *L, const char *metaname)
{
	void **udata = (void **) lua_touserdata(L, 1);
	lua_getmetatable(L, 1);
	luaL_getmetatable(L, metaname);
	bool qual = ( lua_equal(L, -1, -2) == 1);
	lua_pop(L, 2);

	if(qual)
	{
		lua_pushfstring(L, "%s 0x%p", metaname, *udata);
	}

	return qual;
}

static int tostringAll(lua_State *L)
{
	if(     typeName(L, REAPER_void)							) {}
	else if(typeName(L, REAPER_MediaTrack)						) {}
	else if(typeName(L, REAPER_MediaItem)						) {}
	else if(typeName(L, REAPER_MediaItem_Take)					) {}
//	else if(typeName(L, REAPER_MIDI_event_t)					) {}
	else if(typeName(L, REAPER_PCM_source_transfer_t)			) {}
	else if(typeName(L, REAPER_PCM_source_peaktransfer_t)		) {}
	else if(typeName(L, REAPER_midi_realtime_write_struct_t)	) {}
	else if(typeName(L, REAPER_midi_quantize_mode_t)			) {}
	else
	{
		lua_pushstring(L, "Unknown");
	}

	return 1;
}

// these will retrieve or push a (reaper.whatever *) from or to a full userdata

void * toPtr(lua_State *L, int idx, const char *metname)
{
	assert(metname != NULL);
	void **handle = (void **) luaL_checkudata(L, idx, metname);
	assert(handle != NULL);
	return *handle;
}

int pushPtr(lua_State *L, void *ptr, const char *metname)
{
	assert(ptr != NULL);
	void **handle = (void **) lua_newuserdata(L, sizeof(void *));
	assert(handle != NULL);
	*handle = ptr;
	luaL_getmetatable(L, metname);
	lua_setmetatable(L, -2);
	return 1;
}

#define mktopush(TS)									\
	TS * to##TS (lua_State *L, int idx)				\
	{												\
		return (TS *) toPtr(L, idx, REAPER_##TS );	\
	}												\
	int push##TS (lua_State *L, TS *p )			\
	{												\
		return pushPtr(L, p, REAPER_##TS );		\
	}

mktopush(void);
mktopush(MediaTrack);
mktopush(MediaItem);
mktopush(MediaItem_Take);
//mktopush(MIDI_event_t);
mktopush(PCM_source_transfer_t);
mktopush(PCM_source_peaktransfer_t);
mktopush(midi_realtime_write_struct_t);
mktopush(midi_quantize_mode_t);

// makes a possibly-new metatable for our "type" in the registry

void makeMetatable(lua_State *L, const char *mt_name_in_registry)
{
	luaL_newmetatable(L, mt_name_in_registry);	// the possibly-new table is on the top
	lua_pushstring(L, "__tostring");			// table string
	lua_pushcfunction(L, tostringAll);			// table string cfunc
    lua_settable(L, -3);						// table
	lua_pop(L, 1);								// nice clean empty stack
}


// makes possibly-new metatables for our "types" in the registry

void reaperMetatables(lua_State *L)
{
	makeMetatable(L, REAPER_void);
	makeMetatable(L, REAPER_MediaTrack);
	makeMetatable(L, REAPER_MediaItem);
	makeMetatable(L, REAPER_MediaItem_Take);
//	makeMetatable(L, REAPER_MIDI_event_t);
	makeMetatable(L, REAPER_PCM_source_transfer_t);
	makeMetatable(L, REAPER_PCM_source_peaktransfer_t);
	makeMetatable(L, REAPER_midi_realtime_write_struct_t);
	makeMetatable(L, REAPER_midi_quantize_mode_t);

	setupPCM_source(L);
	setupPCM_sink(L);
	setupProjectStateContext(L);
	setupMIDI_eventlist(L);
	setupmidi_Input(L);
	setupmidi_Output(L);
}
